//
//  APIProvider.swift
//  Casecord
//
//  Created by baozi on 2024/1/3.
//

import Foundation
import Moya
import RxSwift
import RxCocoa
import Alamofire

let apiProvider = APIProvider<APITargetType>(callbackQueue: DispatchQueue.main)

class APIProvider<T: TargetType>: MoyaProvider<T> {
    // MARK: - ----------------------------------info
    static func defaultSession() -> Session {
        let configuration = URLSessionConfiguration.default
        configuration.headers = HTTPHeaders()
        return Session(configuration: configuration, startRequestsImmediately: false)
    }
    
    static var defaultPlugins: [PluginType] {
#if DEBUG || TEST
        return [
            APIRequestEnCryptPlugin(),
            APINetworkLoggerPlugin(),
//            NetworkLoggerPlugin(),
        ]
#else
        return [
            APIRequestEncryptPlugin(),
        ]
#endif
    }
    
    static var defaultAPIPlugins: [APIPlugin] {
        [
            /// 后端时间戳修正
            ServerTimePlugin.shared,
            /// 网络请求余额解析
            APIPluginBalance.shared,
        ]
    }
    
    var apiPlugins: [APIPlugin] = []
    // MARK: - ----------------------------------init
    public init(endpointClosure: @escaping EndpointClosure = MoyaProvider<T>.defaultEndpointMapping,
                         requestClosure: @escaping RequestClosure = MoyaProvider<T>.defaultRequestMapping,
                         stubClosure: @escaping StubClosure = MoyaProvider<T>.neverStub,
                         callbackQueue: DispatchQueue? = nil,
                         session: Session = APIProvider<T>.defaultSession(),
                         plugins: [PluginType] = APIProvider<T>.defaultPlugins,
                         apiPlugins: [APIPlugin] = APIProvider<T>.defaultAPIPlugins,
                         trackInflights: Bool = false) {
        self.apiPlugins = apiPlugins
        super.init(endpointClosure: endpointClosure,
                   requestClosure: requestClosure,
                   stubClosure: stubClosure,
                   callbackQueue: callbackQueue,
                   session: session,
                   plugins: plugins,
                   trackInflights: trackInflights
        )
    }
    // MARK: - ----------------------------------system
    // MARK: - ----------------------------------action
    
    func make(_ api: T, callbackQueue: DispatchQueue? = nil, hotPlugin: [APIPlugin] = [], behaviors: Set<APIRequestBehavior>? = []) -> Single<APIResult> {
        // 请求
        let req = self.rx.request(api)
        let res = req.map(APIResult.self)
        // 捕获moya错误转换成自定义error
        var lastSingle = res.catch({ error in
            if let moyaError = error as? MoyaError {
                switch moyaError {
                case .statusCode(let response):
                    // 如果响应状态码在 200-299 范围之外，则抛出服务器错误
                    throw APIError(code: APIError.serverError.code, message: "服务端出错啦～ \(response.statusCode)")
                case .underlying(let nsError, _):
                    // 如果底层 NSError 的域为 NSURLErrorDomain，则抛出连接错误
                    if (nsError as NSError).domain == NSURLErrorDomain {
                        throw APIError.connectionError
                    }
                default:
                    // 抛出moyaerr
                    throw APIError(code: APIError.moyaError.code, data: moyaError)
                }
            }
            throw error
        })
        // 重试机制 moyaError 408重试一次
        lastSingle = lastSingle.retryError(when: { err in
            if APIError.err_timeoffset_1000006 == (err as? APIError) {
                return true
            } else {
                return false
            }
        }, upTo: 1, interval: 1, scheduler: MainScheduler.asyncInstance)
        // 业务的错误吗转Error
        lastSingle = lastSingle.map ({ res -> APIResult in
            if let error = APIError(res) {
                throw error
            } else {
                return res
            }
        })
        
        let apiPlugins = self.apiPlugins + hotPlugin
        // 插件注入
        lastSingle = lastSingle.do(onSuccess: { res in
            apiPlugins.forEach { plugin in
                plugin.didEnd(api: api, behaviors, res, nil)
            }
        }, onError: { error in
            apiPlugins.forEach { plugin in
                plugin.didEnd(api: api, behaviors, nil, error as? APIError)
            }
        }, onSubscribed: {
            apiPlugins.forEach { plugin in
                plugin.didStart(api: api, behaviors)
            }
        })
        return lastSingle
    }
}

extension PrimitiveSequence where Trait == SingleTrait {
    
    func retryError(when: @escaping ((Error) -> Bool), upTo: Int = 3, interval: TimeInterval = 3, scheduler: SchedulerType = MainScheduler.asyncInstance) -> PrimitiveSequence<Trait, Element> {
        precondition(upTo >= 0)
        
        if upTo == 0 {
            return self.primitiveSequence
        }
        return self.catch({ error in
            var p = PrimitiveSequence<Trait, Element>.error(error)
            if !when(error) {
                return p
            }
            let delay = interval / Double(upTo)
            PPNetLog("🔄\(upTo - 1 == 0 ? "第一次重试" : "重试") after \(delay) seconds")
            p = self.delay(.seconds(Int(delay)), scheduler: scheduler).retryError(when: when, upTo: upTo - 1, interval: interval)
            return p
        })
    }
}



